import processing.core.*; 
import processing.xml.*; 

import java.util.StringTokenizer; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class cry3_Contig_Viewer_v3 extends PApplet {



PFont font;
static String filename = "Cry3_associated_peptides_merge.tab";
static String contigCollectionName = "Cry3 associated peptides";

int windowWidth = 1300;
int windowHeight = 710;//760;
float xBorderSize;
float yBorderSize;

float contigSpaceHeight; //this is calculated by partitionVerticalSpace function
float gapsBetweenContigs; //this is calculated by partitionVerticalSpace function
float borderSize; //size of empty space around edge of window
float maxContigWidth; //maximum horizontal space consumed by contig 

ContigCollection contigs;
//MouseWheelEventDemo wheel;

//MenuBox menuBox;

//variables for interactivity
boolean controlKeyPressed;

public void setup()
{
  initZoom();
  size( windowWidth, windowHeight ); 
  addMouseWheelListener(new java.awt.event.MouseWheelListener() { 
    public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { 
      mouseWheel(evt.getWheelRotation());
  }}); 

  font = createFont("SansSerif", 20);
  textFont( font );
  
  xBorderSize = .05f*(float)windowWidth; //make the border a fraction of the width and height
  yBorderSize = .05f*(float)windowHeight;
  
  contigs = new ContigCollection( filename, contigCollectionName, xBorderSize, yBorderSize, windowWidth-xBorderSize, windowHeight-yBorderSize  );
  //wheel = new MouseWheelEventDemo();

  //initZoom(contigs.getNumberOfContigs);
  
}

public void draw()
{
  background( 0 );
  zoomUpdate();
  contigs.draw();
}

//-----------------------------------------------SETUP METHODS------------------------------------------------

//--------------------------------------------END SETUP METHODS-----------------------------------------------

//------------------------------------------INTERACTIVITY METHODS----------------------------------------------

public void keyPressed()
{
  if( key == 'a' )//CONTROL )
    controlKeyPressed = true;
  if( key == 's' )
    contigs.sortAndRearrange();
}

public void keyReleased()
{
  if( key == 'a' )
    controlKeyPressed = false;
}

public void mousePressed()
{
  if( controlKeyPressed )
    contigs.selectionMade(mouseX, mouseY);
}

public void mouseDragged()
{
  println("mouseX = " + mouseX + " mouseY = " + mouseY );
  moveInSpace();
}

public void mouseReleased()
{
  stopMoveInSpace();
}

public void mouseWheel(int delta) {
  println(delta); 
  if( delta > 0 )
    zoomOut();
  else
    zoomIn();
}


//---------------------------------------END INTERACTIVITY METHODS----------------------------------------------- 
/*

for each contig i = 0 ... n-1
      for each contig j=i+1....n-1
	int comparisonVal = contig[ i ].compareTo(contig[ j ]) // higher values, associated with higher similarity in content and order
	int k;
	for each contig k = i+1 \u2026  j-1
		if( comparisonVal < contig[ i ].compareTo( contig[ j+1 ] ) //once the 
			break;
	

		
	while( comparisonVal < contig[i].compareTo(contig[j+1] )

        	if contigs are completely identical in content and order
            		swap contig j with contig i+1
       	if not identical
          //go through from i+1 to j and find where it belongs based on some measure of degree of similarity-- percent of genes in common plus percent of genes with same count

*/

public class Contig extends RectObject implements Comparable
{
  String contigSpecies;
  String contigId;
  String contigStrain;
  
  Gene[] genes;
  
  //float x1, x2, y1, y2;//static position/former position
  //Integrator dynX1, dynX2, dynY1, dynY2;//current position
  float height;
  
  //fields to control look of genes
  Integrator dynMaxNT, dynMinNT;//map gene to this max NT
  float geneHeightToTotalHeightRatio;
  float geneHeight;
  
  int genesAdded;
  
  Contig( String contigSpecies, String contigStrain, String contigId, int geneCount )
  {
    this( contigSpecies, contigStrain, contigId, geneCount, 0.0f, 0.0f, 0.0f, 0.0f, 0 );
  }
  
  Contig( String contigSpecies, String contigStrain, String contigId, int geneCount, int maxNT )
  {
    this( contigSpecies, contigStrain, contigId, geneCount, 0.0f, 0.0f, 0.0f, 0.0f, maxNT );
  }
  
  Contig( String contigSpecies, String contigStrain, String contigId, int geneCount, float x1, float y1, float x2, float y2)
  {
    this( contigSpecies, contigStrain, contigId, geneCount, x1, y1, x2, y2, 0 );
  }
  
  Contig( String contigSpecies, String contigStrain, String contigId, int geneCount, float x1, float y1, float x2, float y2, int maxNT )
  {
    super( x1, y1, x2, y2 );
    
    this.contigId = contigId;
    this.contigSpecies = contigSpecies;
    this.contigStrain = contigStrain;
    
    genes = new Gene[geneCount];
    genesAdded = 0;
    
    this.height = getY2() - getY1();
    
    this.dynMaxNT = new Integrator( (float) maxNT );
    this.dynMinNT = new Integrator( (float) 0 );
    geneHeightToTotalHeightRatio = .2f;//.05;
    geneHeight = calculateGeneHeight( getY1(), getY2() );
    
    //ntMin = 0;
    //ntMax = (int) dynMaxNT.value;
  }
  
  //-------------------------------------------SETUP METHODS---------------------------------------------------
  public void addGene( int start, int stop, String known, String toxinName )
  {
    if( genesAdded >= genes.length )
    {
      println( "too many genes added");
      return;
    }
    
    float gX1 = calculateGeneX1( start, stop, dynMinNT.value, dynMaxNT.value, getX1(), getX2() ); //plus 20 is for label on contig
    float gX2 = calculateGeneX2( start, stop, dynMinNT.value, dynMaxNT.value, getX1(), getX2() );  
    float gY1 = calculateGeneY1( getY1(), getY2() );
    float gY2 = calculateGeneY2( getY1(), getY2() );
    genes[ genesAdded ] = new Gene( this, gX1, gY1, gX2, gY2, start, stop, known, toxinName );
    genesAdded++;
  }
  
  public float calculateGeneX1( int start, int stop, float minNTVal, float maxNTVal, float x1Val, float x2Val )
  {
    if( start < stop )
      return map( start, minNTVal, maxNTVal, x1Val, x2Val );
    return map( stop, minNTVal, maxNTVal, x1Val, x2Val );
  }
  
  public float calculateGeneX2( int start, int stop, float minNTVal, float maxNTVal, float x1Val, float x2Val )
  {
    if( start < stop )
      return map( stop, minNTVal, maxNTVal, x1Val, x2Val );
    return map( start, minNTVal, maxNTVal, x1Val, x2Val );
  }
  
  public float calculateGeneY1(float y1Val, float y2Val )
  {
    return (y2Val + y1Val)/2 - calculateGeneHeight( y1Val, y2Val)/2; 
  }
  
  public float calculateGeneY2(float y1Val, float y2Val )
  {
    return (y2Val + y1Val)/2 + calculateGeneHeight( y1Val, y2Val)/2;
  }
  
  public float calculateGeneHeight( float y1Val, float y2Val )
  {
    return geneHeightToTotalHeightRatio*(y2Val-y1Val);
  }
  //-------------------------------------------END SETUP METHODS------------------------------------------------
  //---------------------------------------------DRAW METHODS---------------------------------------------------
  public void draw()
  {
    super.draw();
    
    //rect
    rectMode( CORNERS );
    noStroke();
    fill( 20, 20, 20);
    if( mouseIsOverPrecise() )
      fill( 50, 50, 50 );

    roundRect(  getX1(), getY1(), getX2(), getY2() );
  
    drawText();
    
    //genes
    for(int i = 0; i < genes.length; i++)
      genes[i].draw();
  }
  
  public void drawText()
  {
    if( mouseIsOverPrecise() )
    {
      fill( 50, 50, 50, 2 );
      textSize( 18.0f );
      float w = textWidth( contigStrain );
      rect( getX1()-1-w, (getY1()+getY2())/2-10, getX1(), (getY1()+getY2())/2+10 );
      fill(255, 255, 255);
      textAlign(RIGHT, CENTER);
      text( contigStrain, getX1()-1, (getY1()+getY2())/2);
    }
    else 
    {
      float h = getY2()-getY1();
      if( h <= 5.0f )
        textSize(5.0f);
      else if( h >= 20 )
        textSize( 18.0f);
      else
        textSize( h );
    
      fill(175, 175, 175);
      textAlign(RIGHT, CENTER);
      text( contigStrain, getX1()-1, (getY1()+getY2())/2);
    }
  }
  //------------------------------------------END DRAW METHODS--------------------------------------------------
  //------------------------------------------INTERACTIVITY METHODS-----------------------------------------------------
  public void highlightGene( int i )
  {
    genes[i].drawHighlightOnly();
  }
  
  public void flip()
  {
    //bool isFlipped = true; 
     //for( int i = 0; i < genes.length; i++)
     //{
       //genes[i].flip();bbbbb
     //}
     
     //float temp = dynMaxNT.value;
     //dynMaxNT.target( dynMinNT.value );
     //dynMinNT.target( temp );
     //updateGenePositionAfterTargeting( c1.x.target, c1.y.value, c1.x.target, c2.y.value, dynMinNT.value, dynMaxNT.value);

  }
  
  //------------------------------------------END INTERACTIVITY METHODS-------------------------------------------------
  //------------------------------------------ANIMATION METHODS--------------------------------------------------
  public void targetX1(float newX1)
  {
    this.target( newX1, c1.y.target, c2.x.target, c2.y.target, dynMaxNT.target );
  }
  
  public void targetY1(float newY1)
  {
    this.target( c1.x.target, newY1, c2.x.target, c2.y.target, dynMaxNT.target );
  }
  
  public void targetX2(float newX2)
  {
    this.target( c1.x.target, c1.y.target, newX2, c2.x.target, dynMaxNT.target ); //used to be value
  }
  
  public void targetY2(float newY2)
  {
    this.target( c1.x.target, c1.y.target, c2.x.target, newY2, dynMaxNT.target );
  }
  
  public void targetMaxNT(int newMaxNT)
  {
    this.target( c1.x.target, c1.y.target, c2.x.target, c2.y.target, (float) newMaxNT );
  }
  
  public void target( float newX1, float newY1, float newX2, float newY2 )
  {
    this.target( newX1, newY1, newX2, newY2, dynMaxNT.target);
  }
  
  public void target (float newX1, float newY1, float newX2, float newY2, float newMaxNT)
  {
    super.target( newX1, newY1, newX2, newY2 );
    
    dynMaxNT.target( newMaxNT );
    
    updateGenePositionAfterTargeting( newX1, newY1, newX2, newY2, dynMinNT.value, newMaxNT ); //note the insert of dynMinNT
  }
  
  public void updateGenePositionAfterTargeting( float newX1, float newY1, float newX2, float newY2,  float newMinNT, float newMaxNT)
  {
    for(int i = 0; i < genes.length; i++)
    {
      float gX1 = calculateGeneX1( genes[i].getStart(), genes[i].getStop(), newMinNT, newMaxNT, newX1, newX2  ); 
      float gX2 = calculateGeneX2( genes[i].getStart(), genes[i].getStop(), newMinNT, newMaxNT , newX1, newX2 ); 
      float gY1 = calculateGeneY1( newY1, newY2 );
      float gY2 = calculateGeneY2( newY1, newY2 );
      genes[i].target( gX1, gY1, gX2, gY2 );
    }
  }
  
  public boolean isTargeting()
  {
    return super.isTargeting() || dynMaxNT.isTargeting(); 
  }
  
  public void update()
  {
   super.update();
   
   if( dynMaxNT.isTargeting() )
     dynMaxNT.update();
  }
  //---------------------------------------END ANIMATION METHODS-------------------------------------------------
  
  //----------------------------------------------INTERACTIVITY METHODS------------------------------------------
  
  public boolean isOver( float x, float y )
  {
    return ( x >= (getX1() - 5) && x <= (getX2() + 5)  && y >= (getY1()-10) && y <= (getY2()+10) );
  }
  //-------------------------------------------END INTERACTIVITY METHODS-----------------------------------------
  
  //--------------------------------------------SETTERS----------------------------------------------------------
  
  public void assignGeneColors( int geneIdx, int fillColor, int highlightColor )
  {
    genes[geneIdx].setColors( fillColor, highlightColor );
  }
  
  public void assignGeneColor( int geneIdx, int fillColor )
  {
    genes[geneIdx].setColor( fillColor );
  }

  //----------------------------------------END SETTERS----------------------------------------------------------
  
  //--------------------------------------------GETTERS----------------------------------------------------------
  public int getStop()
  {
    //find stop.. in this case, last gene
    int maxStop = 0;
    for(int i = 0; i < genes.length; i++)
    {
      if( genes[i].getGreatestNT() > maxStop )
        maxStop = genes[i].getGreatestNT();
    }
    
    return maxStop;
  }
  
  public int getMaxNT()
  {
    return (int) dynMaxNT.value;
  }
  
  public Gene getGene( float x, float y )
  {
    //Gene g = null;
    for(int i = 0; i < genes.length; i++)
    {
      if( genes[i].isOver( x, y ) )
      {
        return genes[i];//g = genes[i];
      }
    }
    
    return null;//g;
  }
  
  public Gene getGene(int i)
  {
    if( i >= genes.length )
      return null;
    return genes[i];
  }
  
  public int getNumberOfGenes()
  {
    return genes.length;
  }
  
  //-------------------------------------------END GETTERS-------------------------------------------------------
  //-------------------------------------------UTILITY METHODS---------------------------------------------------
  public String toString()
  {
    return "" + contigId;
  }
  
  public int compareTo( Object o )
  {
    Contig other = (Contig) o;
    if( genes.length != other.getNumberOfGenes() )
      return -1;

    //look to see if it has an identical gene order
    int identicalGenes = 0;
    for(int i = 0 ; i < genes.length; i++)
    {
      if( genes[i].equals( (Gene) other.getGene(i )) );
        identicalGenes++;
    }
    
    if( identicalGenes == genes.length )
    {
      println( "identical" );
      return 0;
    }
    
    identicalGenes = 0;
    for(int i = 0 ; i < genes.length; i++)
    {
      if( genes[i].equals( (Gene) other.getGene( genes.length-1-i) ))
        identicalGenes++;
    }
    
    if( identicalGenes == genes.length )
      return 0;
     
    return -1;
    //determine percent of genes in this that are in the same order in other- looking at forward and reverse orders
    
    
  }
 //-------------------------------------------UTILITY METHODS---------------------------------------------------
}


public class ContigAndGeneReader
{
  /*
  Contig and Gene Reader class will use these column values
  to locate elements in the file 
  */
  int idColumn = 0; //eg. Bt_EG10144i_contig00236_G1.1.pep
  int contigColumn = 1;//eg. 1 to 96
  int startColumn = 2;//nucleotide (from 0) at which gene starts
  int stopColumn = 3;//nucleotide (from 0) at which gene ends
  int knownnessColumn = 4;//either KNOWN or PUTATIVE
  int toxinColumn = 5;//name of toxin, or NA
  
  DataTable dataTable;

  /*
  CONSTRUCTOR
  loads file and parses into 2D data array
  nothing in this constructor is contig specific
  */
  ContigAndGeneReader( String filename )
  {
    println( "opening file and reading data" );
    
    dataTable = new DataTable( filename );

  }
  
  //--------------------------------------GETTERS-------------------------------------
  /*
  Method: getNumberOfContigs
  Returns: numberOfContigs
  */
  public int getNumberOfContigs()
  {
     //NOTE:  start by showing 10 
     return dataTable.getMaxIntInColumn( contigColumn ); 
     //return 10;
  }
  
  
  public Contig getContig( int i )
  {
    if( i >= dataTable.getMaxIntInColumn( contigColumn ))//TO DO:  make sure it is >= not just >
    {
      println( "error: requested contig exeeds maximum size" );
      return null;
    }
    
    //get range of rows in which contig can be found
    int startRow = dataTable.getFirstRowOfIntElt( contigColumn, i+1 ); //assume indexing 0 - n-1
    int endRow = dataTable.getLastRowOfIntElt( contigColumn, i+1 );
    
    if( startRow == -1 || endRow == -1 ) //make sure range of rows is valid
    {
      println( "error: requested contig not matched in table" );
      return null;
    }
    
    //Create and add Genes to contig
    String unprocessedContigName = dataTable.getStringElement( startRow, idColumn );//get raw name
    String[] contigIds = processContigName( unprocessedContigName );//process name into pieces
    
    //count the genes for this contig
    int geneCount = 0;
    for(int row = startRow; row <= endRow; row++)//loop through all rows in which this contig is possibly present
    {
      if( dataTable.getIntElement( row, contigColumn ) == (i+1) ) //make sure it matches contig number
      {
        geneCount++;
      }  
    }
    
    int maxNT = dataTable.getMaxIntInColumn( stopColumn );//default start max nt value
    Contig c = new Contig( contigIds[0], contigIds[1], contigIds[2], geneCount, maxNT );
    
    //then add gene
    for(int row = startRow; row <= endRow; row++)//loop through all rows in which this contig is possibly present
    {
      if( dataTable.getIntElement( row, contigColumn ) == (i+1) ) //make sure it matches contig number
      {
        int start = dataTable.getIntElement( row, startColumn );
        int end = dataTable.getIntElement( row, stopColumn );
        String known = dataTable.getStringElement( row, knownnessColumn );
        String toxinName = dataTable.getStringElement( row, toxinColumn );
       
        c.addGene( start, end, known, toxinName );
      }  
    }
    
    return c;
  }
  
  //------------------------------------END GETTERS-----------------------------------
  
  //------------------------------------DATA PROCESSING METHODS-----------------------
  public String[] processContigName( String unprocName )
  {     
    String[] temp = new String[3];
    StringTokenizer st = new StringTokenizer( unprocName , "_" );
    temp[0] = st.nextToken(); //species name
    temp[1] = st.nextToken(); //string name
    temp[2] = st.nextToken();//contig name 
    
    return temp;
  }
  
  //--------------------------------END DATA PROCESSING METHODS-----------------------
  //-----------------------------------UTILITY METHODS--------------------------------
  /*
  Method: toString
  Action: converts object to string
  Parameters: none
  Returns: string value of object
  Note: println method calls this
  */
  public String toString()
  {
    return dataTable.toString();
  }
  //----------------------------------END UTILITY METHODS-----------------------------

}


public class ContigCollection
{
  //----------------------------------------------------------------FIELDS---------------------------------------------------------------//
  /*
  CONTIG COLLECTION LOCATION FIELDS
    - static location fields
  */
  float x1, y1, x2, y2;//static position-- to keep track of initial position
  Integrator dynX1, dynY1, dynX2, dynY2; //current/moving position
  
  /*
  CONTIG SPACING FIELDS
  */
  float contigSpaceHeight; //this is calculated by partitionVerticalSpace function
  float gapsBetweenContigs; //this is calculated by partitionVerticalSpace function
  float xBorderSize; //size of empty space around edge of ContigCollection
  float yBorderSize;
  float maxContigWidth; //maximum horizontal space consumed by contig 
  float[] y1Positions;
  float[] y2Positions;
  int numOfContigsOnScreen;
  int maxNTOnScreen;
  
  /*
  ABOUT CONTIG COLLECTION FIELDS
  */
  String name;
  
  /*
  FIELDS TO ACCESS AND STORE CONTIGS
  */
  ContigAndGeneReader reader;
  Contig[] contigs;
  HashMap mapNameToPairArray;
  HashMap mapLengthToPairArray;
  
  int uniqueNamesCount;
  int uniqueLengthsCount;
  
  int cryCount;
  int ticCount;
  
  ArrayList cryGenes;
  ArrayList ticGenes;
  
  int[] cryColors;
  int[] ticColors;
 
  class Pair
  {
    int contigNum;
    int geneNum; 
    
    Pair( int contigNum, int geneNum )
    {
      this.contigNum = contigNum;
      this.geneNum = geneNum;
    }
  }
  
  //--------------------------------------------------------------END FIELDS--------------------------------------------------------------//
  //--------------------------------------------------------------CONSTRUCTORS--------------------------------------------------------------//
  /*
  CONSTRUCTOR
    Parameters:
      - filename = name of file in which contig data found
      - name = label to give collection of contigs
      - x1, x2, y1, y2 = positional information about the contig collection
  */
  ContigCollection( String filename, String name, float x1, float y1, float x2, float y2 )
  {
    //position contig collection in space
    this.x1 = x1;
    this.y1= y1;
    this.x2 = y2;
    this.y1 = y1;
    this.y2 = y2;
    
    dynX1 = new Integrator( x1 );
    dynY1 = new Integrator( y1 );
    dynX2 = new Integrator( x2 );
    dynY2 = new Integrator( y2 );
  
    //give collection a name
    this.name = name;
    
    //open and read file
    reader = new ContigAndGeneReader( filename );
    println( reader ); //show contents of file
  
    println( reader.getNumberOfContigs() ); //check for number of contigs
  
    //setup the contig viewing space
    xBorderSize = .01f*(x2-x1); //this sets distance between all elements and the edge of the contig collection
    yBorderSize = .01f*(y2-y1);
    maxContigWidth = (dynX2.value-dynX1.value) - 2*xBorderSize;
    partitionVerticalSpace( reader.getNumberOfContigs(), .5f, borderSize); //based on number of contigs, divide space vertically
    
    //CREATE ARRAY OF CONTIGS 
    contigs = new Contig[ reader.getNumberOfContigs() ];
    for(int i = 0; i < reader.getNumberOfContigs(); i++ )
    {
      contigs[i] = reader.getContig( i );
      contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    }
    
    //maxNT already set in Contig and gene reader, but now I want to target maxNT of elements on screen
    //numOfContigsOnScreen = 10;
    numOfContigsOnScreen = reader.getNumberOfContigs();
    
    findAndTargetOnScreenMaxNT(); //will reset so onscreen max nt is only thing considered
    
    createHashMaps(); 
    
    assignGeneColors();//based on name
    
    //sortContigs();
    //for(int i = 0; i < reader.getNumberOfContigs(); i++ )
    //{
      //contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    //}
    
    
  }//end constructor
  //------------------------------------------------------------END CONSTRUCTORS--------------------------------------------------------------// 
  
  //--------------------------------------------------------------SETUP METHODS--------------------------------------------------------------//
  /*
  Method:  partitionVerticalSpace
  Parameters: numberOfVerticalUnits
  Action: divides the vertical space evenly based on 
    - the number of vertical units
    - the ratio between size of the gap between units and vertical unit size
    - the number of gaps at top
    - the number of gaps at bottom
  
    Eq1: availableHeight = totalHeight - 2*borderSize
    Eq2: gapSize/vertUnitSize = ratio
       gapSize = ratio*vertUnitSize
    Eq3: availableHeight = numOfGaps*gapSize + numOfVerticalUnits*vertUnitSize
         availableHeight = numOfGaps*(ratio*vertUnitSize) + numOfVerticalUnits*vertUnitSize
         availableHeight = vertUnitSize( numOfGaps*ratio + numOfVerticalUnits )
       vertUnitSize = availableHeight / ( numOfGaps*ratio + numOfVerticalUnits )
  */
  public void partitionVerticalSpace( int numberOfContigs, float gapToContigRatio, float borderSize )
  {
    float availableHeight = (dynY2.value-dynY1.value) - xBorderSize - yBorderSize; //remove the border from top and bottom
    int numOfGaps = numberOfContigs - 1;
  
    contigSpaceHeight = availableHeight / ( numOfGaps*gapToContigRatio + numberOfContigs );
    gapsBetweenContigs = gapToContigRatio * contigSpaceHeight;
  
    println( "contigSpaceHeight = " + contigSpaceHeight + " gapsBetweenContigs " + gapsBetweenContigs );
    
    y1Positions = new float[ numberOfContigs ];
    y2Positions = new float[ numberOfContigs ];
    
    //set position of first element
    y1Positions[0] = dynY1.value + yBorderSize;
    y2Positions[0] = y1Positions[0] + contigSpaceHeight;
    for( int i = 1; i < numberOfContigs; i++ )//then set position of other elements
    {
      y1Positions[i] = y2Positions[i-1] + gapsBetweenContigs; //previous end coordinate + distance between contigs
      y2Positions[i] = y1Positions[i] + contigSpaceHeight; //start + height of contig
    }
  }//end partitionVerticalSpace
  
  public void findAndTargetOnScreenMaxNT()
  {
    maxNTOnScreen = 0;
    for(int i = 0; i < numOfContigsOnScreen; i++)
    {
      if( contigs[i].getStop() > maxNTOnScreen )
        maxNTOnScreen = contigs[i].getStop();
    }
    
    //then reset to target new max nt
    for(int i = 0; i < numOfContigsOnScreen; i++)
    {
      contigs[i].targetMaxNT( maxNTOnScreen );
    }
  }
  
  public void createHashMaps()
  {
    uniqueNamesCount = 0;
    uniqueLengthsCount = 0;
    
    mapNameToPairArray = new HashMap();
    mapLengthToPairArray = new HashMap();
    
    cryGenes = new ArrayList();
    ticGenes = new ArrayList();

    // fill maps with array lists for all names
    for(int i = 0; i < contigs.length; i++ )//for all contigs
    {
      for(int j = 0; j < contigs[i].getNumberOfGenes() ; j++)//for all genes in the contig
      {
        //get gene
        Gene g = contigs[i].getGene(j);
        String name = g.getToxinName();
        int len = g.getLength();
        
        Pair pair = new Pair( i, j ); //create a pair for that gene
        
        //FIRST CHECK NAME MAP
        //see if this name has previously been encountered, 
        //if so, add the pair to the array list already created
        ArrayList namePairs = (ArrayList) mapNameToPairArray.get( name );
        if( namePairs != null )
        {
          namePairs.add( (Object) pair ); //add this pair to array list
        }
        else //need to create pair array list and add this element to it
        {
          namePairs = new ArrayList();
          namePairs.add( pair );
          
          mapNameToPairArray.put( name, namePairs );
          
          uniqueNamesCount++;
          
          if( name.length() >= 3)
          {
            if( name.substring(0,3).equalsIgnoreCase( "cry" ) )
            {
              cryGenes.add( name );
              cryCount++;
              //contigs[i].assignGeneColor(j, 0x4CC4D4 );
            }
            if( name.substring(0,5).equalsIgnoreCase( "ictic" ) ) 
            {
              ticGenes.add( name );
              ticCount++;
              //contigs[i].assignGeneColor( j, 0x43D4A0 );
            }
          }
        }
        
        //THEN CHECK LENGTH MAP
        ArrayList lengthPairs = (ArrayList) mapLengthToPairArray.get( len );
        if( lengthPairs != null )
        {
          lengthPairs.add( pair ); //add this pair to array list
        }
        else //need to create pair array list and add this element to it
        {
          lengthPairs = new ArrayList();
          lengthPairs.add( pair );
          
          mapLengthToPairArray.put( len, lengthPairs );
          uniqueLengthsCount++;
        }
      }
    }
    
    println( "unique names count = " + uniqueNamesCount + " mapNameToPairArray size = " + mapNameToPairArray.size() );
    println( "unique lengths count = " + uniqueLengthsCount + " mapLengthToPairArray size = " + mapLengthToPairArray.size() );
    println( "cry count = " + cryCount + " ticCount = " + ticCount );
  }//end method
  
  public void assignGeneColors()
  {
  
    cryColors = new int[ cryCount ];
    ticColors = new int[ ticCount ];  
    
     // fill maps with array lists for all names
    for(int i = 0; i < contigs.length; i++ )//for all contigs
    {
      for(int j = 0; j < contigs[i].getNumberOfGenes() ; j++)//for all genes in the contig
      {
        //get gene
        Gene g = contigs[i].getGene(j);
        String name = g.getToxinName();
        int len = g.getLength();
          
        if( name.length() >= 3)
        {
           if( name.substring(0,3).equalsIgnoreCase( "cry" ) )
           {
             contigs[i].assignGeneColor(j, 0xFF589EE8);//0xFF5386D4 );//0xFF4CC4D4 );
           }
           if( name.substring(0,5).equalsIgnoreCase( "ictic" ) ) 
           {
             contigs[i].assignGeneColor( j, 0xFF4CE89B );//0xFFD453B4 );//0xFFA053D4);//0xFF43D4A0 );
           }
         }
       }
     }

  }
  
  public void colorGenesByGeneName()
  {
    //for all gene instances in gene hash map
      //get gene
      //assign color 
  }
  
  public void sortContigs()
  {
    Arrays.sort( contigs );
    //for each contig i = 0 ... n-1
      //for each contig j=1....n-1
        //if contigs are completely identical in content and order
            //swap j with i+1
        //if not identical
          //go through from i+1 to j and find where it belongs based on some measure of degree of similarity-- percent of genes in common plus percent of genes with same count
    
    
    //now contigs that are identical will be next to each other with similar contigs close 
        
    
    
  }
  //-----------------------------------------------------------END SETUP METHODS--------------------------------------------------------------//
  
  //-------------------------------------------------------------DRAW METHODS-----------------------------------------------------------------//
  public void draw()
  {
    Gene highlightedGene = null; //store gene that is hovered over so can find other instances
    
    //draw contigs
    for(int i = 0; i < contigs.length; i++)
    {
      contigs[i].draw();
      if( contigs[i].mouseIsOverPrecise() ) //if mouse is over contig, get gene 
      {
        highlightedGene = contigs[i].getGene( mouseX, mouseY ); //get the gene under the mouse
        //if( highlightedGene != null )
          //println( "  inside for loop not null");
      }
    }
    
    if( highlightedGene != null )
    {
        //println( "  outside for loop not null");
          highlightAllInstancesOfGene( highlightedGene );
          highlightedGene.draw(); //redraw
    }
          
    //take care of any moving objects
    if( this.isTargeting() )
    {
      this.update();
    }
  }
  
  public void highlightAllInstancesOfGene( Gene g )
  {
    String name = g.getToxinName();
    if( name.equalsIgnoreCase( "na" ) ) //use length hash table to find other gene instances
    {
      int len = g.getLength();
      ArrayList al = (ArrayList) mapLengthToPairArray.get( len );
      if( al == null )
      {
        println( "error- no genes with that length" );
        return;
      }
      for(int i = 0; i < al.size(); i++)
      {
        Pair p = (Pair) al.get(i);
        contigs[p.contigNum].highlightGene( p.geneNum );
      }
    }
    else //use name hash table to find other gene instances
    {
      ArrayList al = (ArrayList) mapNameToPairArray.get(name );
      if( al == null )
      {
        println( "error- no genes with that name" );
        return;
      }
      for(int i = 0; i < al.size(); i++)
      {
        Pair p = (Pair) al.get(i);
        contigs[p.contigNum].highlightGene( p.geneNum );
      }
    }
  }
  //-----------------------------------------------------------END DRAW METHODS---------------------------------------------------------------//
  //------------------------------------------------------------INTERACTION METHODS----------------------------------------------------------//
  public void selectionMade( float mx, float my )
  {
    println( "gene selected!");
    Gene targetGene = null;
    //find gene assoc with x and y
    for(int i = 0; i< contigs.length; i++)
    {
      if( contigs[i].mouseIsOverPrecise() )//isOverPrecise(mx, my) )
      {
        println( "is over contig" );
        targetGene = contigs[i].getGene(mouseX, mouseY); //should be mx and my
        if( targetGene == null )
          return;
        println("is over gene");
        float gX1 = targetGene.getActualX1();
        float gX2 = targetGene.getActualX2();
        float geneCenter = (gX1+gX2)/2;
        float distToCenter = (x2+x1)/2 - geneCenter; 
        println( "distance to center = " + distToCenter );

        //contigs[i].target( contigs[i].getX1()+distToCenter, contigs[i].getY1(), contigs[i].getX2()+distToCenter, contigs[i].getY2() );

        ArrayList al = getArrayListForGene( targetGene );
        if( al == null )
        {
          println( "error- no genes with that name or length" );
          return;
        }
        for(int j = 0; j < al.size(); j++)
        {
          Pair p = (Pair) al.get(j);
          Gene g = (Gene) contigs[p.contigNum].getGene( p.geneNum);
          
          //if points different direction, contig should be flipped, then can align
          if( g.pointsRight != targetGene.pointsRight )
          {
            println( "flip " + p.contigNum);
            contigs[p.contigNum].flip();
          }
          
          gX1 = g.getActualX1();
          gX2 = g.getActualX2();
          float gCenter = (gX1+gX2)/2;
          distToCenter = geneCenter - gCenter;
          
          contigs[p.contigNum].target( contigs[p.contigNum].getActualX1()+distToCenter, contigs[p.contigNum].getActualY1(), contigs[p.contigNum].getActualX2()+distToCenter, contigs[p.contigNum].getActualY2() );
        }
        return;
         
      }
     
    }
  }
  //-----------------------------------------------------------END INTERACTION METHODS----------------------------------------------------------
  //-----------------------------------------------------------ANIMATION METHODS----------------------------------------------------------------
  public void targetX1(float newX1)
  {
    this.target( newX1, dynY1.target, dynX2.target, dynY2.target );
    //dynX1.target( newX1 );
  }
  
  public void targetY1(float newY1)
  {
    this.target( dynX1.target, newY1, dynX2.target, dynY2.target );
    //dynY1.target( newY1 );
  }
  
  public void targetX2(float newX2)
  {
    this.target( dynX1.target, dynY1.target, newX2, dynY2.target );
    //dynX2.target( newX2 );
  }
  
  public void targetY2(float newY2)
  {
    this.target( dynX1.target, dynY1.target, dynX2.target, newY2 );
    //dynY2.target( newY2 );
  }
  
  public void target( float newX1, float newY1, float newX2, float newY2 )
  {
    dynX1.target( newX1 );
    dynX2.target( newX2 );
    dynY1.target( newY1 );
    dynY2.target( newY2 );
  }
  
  public boolean isTargeting()
  {
    return dynX1.isTargeting() || dynX2.isTargeting() || dynY1.isTargeting() || dynY2.isTargeting(); //if any one element targeting, keep updating  
  }
  
  public void update()
  {
    if( dynX1.isTargeting() )
      dynX1.update();
    if( dynX2.isTargeting() )
      dynX2.update();
    if( dynY1.isTargeting() )
      dynY1.update();
    if( dynY2.isTargeting() )
      dynY2.update();
  }
  //---------------------------------------------------------END ANIMATION METHODS--------------------------------------------------------------
  
  //---------------------------------------------------------------GETTERS----------------------------------------------------------------------
  
  public int getNumberOfContigs()
  {
    return reader.getNumberOfContigs();
  }
  
  public ArrayList getArrayListForGene( Gene g )
  {
    String name = g.getToxinName();
    if( name.equalsIgnoreCase( "na" ) ) //use length hash table to find other gene instances
    {
      int len = g.getLength();
      return (ArrayList) mapLengthToPairArray.get( len );
    }
    //else return by name
    return (ArrayList) mapNameToPairArray.get(name );
  }
  
  //--------------------------------------------------------------END GETTERS-------------------------------------------------------------------
  
  public void sortAndRearrange()
  {
    sortContigs();
    for(int i = 0; i < contigs.length; i++ )
    {
      contigs[i].target( dynX1.value+xBorderSize, y1Positions[i], dynX2.value-xBorderSize, y2Positions[i] );
    }
  }
}

class Coordinate
{
  //coordinates are not static, 
  Integrator x;
  Integrator y;
  
  Coordinate( float x, float y )
  {
    this.x = new Integrator( x );
    this.y = new Integrator( y );
  }
  
  Coordinate( int x , int y )
  {
    this((float) x, (float) y );
  }
  
  //--------------------------TARGETING AND MOVING-----------------------------------
  public void target( float newX, float newY )
  {
    x.target( newX );
    y.target( newY );
  }
  
  public void target( int newX, int newY )
  {
    this.target( (float) newX, (float) newY );
  }
  
  public void targetX( float newX )
  {
    this.target( newX, y.target );
  }
  
  public void targetY( float newY )
  {
    this.target( x.target, newY );
  }
  
  public boolean isTargeting()
  {
    return x.isTargeting() || y.isTargeting();
  }
  
  public void update()
  {
    if( x.isTargeting() )
      x.update();
    if( y.isTargeting() )
      y.update();
  }
  
  public void moveRight( float diff )
  {
    targetX( x.value+diff );
  }
  
  public void moveRight( int diff )
  {
    this.moveRight( (float)diff);
  }
  
  public void moveLeft( float diff )
  {
    targetX( x.value-diff );
  }
  
  public void moveLeft( int diff )
  {
    this.moveLeft( (float)diff);
  }
  
  public void moveUp( float diff )
  {
    targetY( y.value-diff );
  }
  
  public void moveUp( int diff )
  {
    this.moveUp( (float)diff);
  }
  
  public void moveDown( float diff )
  {
    targetY( y.value+diff );
  }
  
  public void moveDown( int diff )
  {
    this.moveDown( (float)diff);
  }
  //------------------------END TARGETING AND MOVING---------------------------------
  
  public float getX()
  {
    return tx( x.value );
    //return x.value;
  }
  
  public float getY()
  {
    return ty( y.value );
    //return y.value;
  } 
  
   public float getActualX()
  {
    //return tx( x.value );
    return x.value;
  }
  
  public float getActualY()
  {
    //return ty( y.value );
    return y.value;
  } 
}


public class DataTable
{
  int numOfRows, numOfColumns;
  String[][] data;
  String[] columnNames;
  
  String[] maxStringEltInColumn;
  float[] maxFloatEltInColumn;
  int[] maxIntEltInColumn;
  
  DataTable( String filename )
  {
    //LOAD DATA
    String[] rows = loadStrings( filename );
    String[] columns = split( rows[0], "\t" ); //split by tabs
    
    columnNames = subset(columns, 0);//gets all column names from 0th column to last column
    numOfColumns = columnNames.length; //num of columns is equal to columnNames.length
    
    data = new String[ rows.length-1 ][ ];//start out n arrays, where n equals number of rows
    
    println( "I see " + rows.length + " lines in your file");
    println( "With " + numOfColumns + " columns separated by tab delimiters" ); 
    
    // split rows into columns
    // start reading at row 1, because the first row was only the column headers
    for (int i = 1; i < rows.length; i++) {
      if (trim(rows[i]).length() == 0) {
        continue; // skip empty rows
      }
      if (rows[i].startsWith("#")) {
        continue;  // skip comment lines
      }
      
      // split the row on the delimiter
      String[] pieces = split(rows[i], '\t');
      
      // copy data into the table starting at pieces[0]
      data[numOfRows] = subset(pieces, 0);

      // increment the number of valid rows found so far
      numOfRows++;      
    }
    // resize the 'data' array as necessary
    data = (String[][]) subset(data, 0, numOfRows);
    
    //COMPUTE MAXIMUMS
    maxStringEltInColumn = new String[ numOfColumns ];
    maxIntEltInColumn = new int[ numOfColumns ];
    maxFloatEltInColumn = new float[ numOfColumns ]; 
    for(int col = 0; col < numOfColumns; col++)
    {
        maxStringEltInColumn[ col ] = data[ 0 ][ col ];
        maxIntEltInColumn[ col ] = parseInt( data[ 0 ][ col ] );
        maxFloatEltInColumn[ col ] = parseFloat( data[ 0 ][ col ] );
        
        for(int row = 0; row < numOfRows; row++)
        {
          if( maxStringEltInColumn[ col ].compareTo( data[row][col] ) < 0 )
            maxStringEltInColumn[col] = data[row][col];
          if( maxIntEltInColumn[ col ] < parseInt( data[row][col] ) )
            maxIntEltInColumn[col] = parseInt( data[row][col] );
          if( maxFloatEltInColumn[ col ] < parseFloat( data[row][col] ) )
            maxFloatEltInColumn[col] = parseFloat( data[row][col] );  
        }
    }
  }
  
  //---------------------------------------------------GETTERS--------------------------------------------------
  
  //---------------------BASIC GETTERS----------------
  /*
  Method: getStringElement
  Parameters:  row and column
  Returns:  String elt at that position
  */
  public String getStringElement( int row, int col )
  {
    return data[ row ][ col ];
  }
  
   /*
  Method: getIntElement
  Parameters:  row and column
  Returns:  Int elt at that position
  User beware:  if elt is not an int, will return NAN
  */
  public int getIntElement( int row, int col )
  {
    return parseInt( data[ row ][ col ] ); 
  }
  
   /*
  Method: getFloatElement
  Parameters:  row and column
  Returns:  Float elt at that position.  
  User beware:  if elt is not a float, will return NAN
  */
  public float getFloatElement( int row, int col )
  {
    return parseFloat( data[ row ][ col ] );
  }
  //-------------------END BASIC GETTERS--------------
  //---------------------GET MAXIMUM------------------
  /*
  Method: getStringElement
  Parameters:  row and column
  Returns:  String elt at that position
  */
  public String getMaxStringInColumn( int col )
  {
    return maxStringEltInColumn[ col ];
  }
  
   /*
  Method: getIntElement
  Parameters:  row and column
  Returns:  Int elt at that position
  User beware:  if elt is not an int, will return NAN
  */
  public int getMaxIntInColumn( int col )
  {
    return maxIntEltInColumn[ col ] ; 
  }
  
   /*
  Method: getFloatElement
  Parameters:  row and column
  Returns:  Float elt at that position.  
  User beware:  if elt is not a float, will return NAN
  */
  public float getMaxFloatInColumn( int col )
  {
    return maxFloatEltInColumn[ col ] ;
  }
  //-------------------END GET MAXIMUM----------------
  //---------------------GET FIRST OF-----------------
   /*
  Method: getFirstRowOfIntElt
  Parameters:  int column, int elt
  Returns:  row at which integer element first appears
  User beware:  if elt is not an int, will return -1, because won't be found
  */
  public int getFirstRowOfIntElt( int col, int elt )
  {
    for(int row = 0; row < data.length; row++)
    {
      if( parseInt(data[row][col]) == elt )
        return row;
    }
    return -1; //not found
  }
  
  /*
  Method: getFirstRowOfFloatElt
  Parameters:  int column, float elt
  Returns:  row at which float element first appears
  User beware:  if elt is not a float, will return -1, because won't be found
  */
  public int getFirstRowOfFloatElt( int col, float elt )
  {
    for(int row = 0; row < data.length; row++)
    {
      if( parseFloat(data[row][col]) == elt )
        return row;
    }
    return -1; //not found
  }
  
  /*
  Method: getFirstRowOfStringElt
  Parameters:  int column, String elt
  Returns:  row at which String element first appears
  User beware:  if elt is not a String, will return -1, because won't be found
  */
  public int getFirstRowOfStringElt( int col, String elt )
  {
    for(int row = 0; row < data.length; row++)
    {
      if( data[row][col].compareTo( elt ) == 0 )
        return row;
    }
    return -1; //not found
  }
  //-------------------END GET FIRST OF---------------
  
  //---------------------GET LAST OF-----------------
   /*
  Method: getLastRowOfIntElt
  Parameters:  int column, int elt
  Returns:  row at which integer element last appears
  User beware:  if elt is not an int, will return -1, because won't be found
  */
  public int getLastRowOfIntElt( int col, int elt )
  {
    int rowToReturn = -1;
    for(int row = 0; row < data.length; row++)
    {
      if( parseInt(data[row][col]) == elt )
        rowToReturn = row;
    }
    return rowToReturn; //not found
  }
  
  /*
  Method: getLastRowOfFloatElt
  Parameters:  int column, float elt
  Returns:  row at which float element last appears
  User beware:  if elt is not a float, will return -1, because won't be found
  */
  public int getLastRowOfFloatElt( int col, float elt )
  {
    int rowToReturn = -1;
    for(int row = 0; row < data.length; row++)
    {
      if( parseFloat(data[row][col]) == elt )
        rowToReturn = row;
    }
    return rowToReturn; //not found
  }
  
  /*
  Method: getLastRowOfStringElt
  Parameters:  int column, String elt
  Returns:  row at which String element last appears
  User beware:  if elt is not a String, will return -1, because won't be found
  */
  public int getLastRowOfStringElt( int col, String elt )
  {
    int rowToReturn = -1;
    for(int row = 0; row < data.length; row++)
    {
      if( data[row][col].compareTo( elt ) == 0 )
        rowToReturn = row;
    }
    return rowToReturn; //not found
  }
  //-------------------END GET LAST OF---------------
  //------------------------------------------------END GETTERS--------------------------------------------------
  
  //------------------------------------------------UTILITY METHODS----------------------------------------------
    
  /*
  outputs data to stdout
  */
  public String toString()
  {
    String toReturn = "";
    for(int i =0; i < data.length; i++)
    {
      toReturn += i + "\t";
      for(int j = 0; j < data[i].length; j++)
      {
        toReturn += data[i][j] + "\t";
      }
      toReturn += "\n";
    }
    return toReturn;
  }
  
  //------------------------------------------------END UTILITY METHODS------------------------------------------
  
}

public void roundRect(float x1, float y1, float x2, float y2 )
{ 
  if( x1 > x2 )
  {
    float temp = x1;
    x1 = x2;
    x2 = temp;
  }
  if( y1 > y2 )
  {
    float temp = y1;
    y1 = y2;
    y2 = temp;
  }
  smooth();
  float perc = .3f;
  float rad = abs( perc*(x2-x1) );
  if( (y2-y1) < (x2-x1) )
  {
    rad = abs( perc*(y2-y1) );
  }  
  
  rectMode( CORNERS );
  rect( x1+rad, y1, x2-rad, y2 );
  rect( x1, y1+rad, x2, y2-rad );
  
  //ellipse( x1+dispFromX, y1+dispFromY, 2*dispFromX, 2*dispFromY );
  ellipseMode( CORNERS );
  ellipse( x1, y1, x1+2*rad, y1+2*rad );
  ellipse( x1, y2, x1+2*rad, y2-2*rad );
  ellipse( x2, y2, x2-2*rad, y2-2*rad );
  ellipse( x2, y1, x2-2*rad, y1+2*rad );
}

public void drawArrow( float x1, float y1, float x2, float y2)
{
  strokeWeight( (int) abs(y2-y1) );
  line(x1, y1, x2, y2);
  pushMatrix();
  translate(x2, y2);
  float a = atan2(x1-x2, y2-y1);
  rotate(a);
  //line(0, 0, -10, -10);
  //line(0, 0, 10, -10);
  float h = abs(y2-y1)*.3f;
  triangle( 1, 1, -1*h, -1*h, h, -1*h );
  popMatrix();
}
public class Gene extends RectObject
{
  int colorForKnownGene;
  int colorForUnknownGene;
  int fillColor;
  int highlightColor;
  
  int start;
  int stop; 
  int length;
  boolean pointsRight;
  String known; 
  String toxinName;
  
  //float x1, x2, y1, y2;//static position/former position
  //Integrator dynX1, dynX2, dynY1, dynY2;//current position
  
  Contig parentContig;
  
  Gene( Contig parentContig, float x1, float y1, float x2, float y2, int start, int stop, String known, String toxinName )
  {
    super( x1, y1, x2, y2 );
    
    this.start = start;
    this.stop = stop;
    this.length = abs(stop-start);
    this.known = known;
    this.toxinName = toxinName.trim();
    
    this.parentContig = parentContig;
    
    pointsRight = true;
    if( start > stop )
      pointsRight = false; 
    
    colorForKnownGene = 0xFF5386D4;
    colorForUnknownGene = 0xFFAEAEAD;//0xFFABAD9D;//0x7D646464;
    if( known.equalsIgnoreCase( "known" ) || known.equalsIgnoreCase( "other" ) || known.equalsIgnoreCase( "putative" ))
    {
      fillColor = colorForKnownGene;
    }
    else
      fillColor = colorForUnknownGene;
    
     highlightColor = 0x7B636363;//0x4B636363;//0x33FFF55B;
  }
   //---------------------------------------------DRAW METHODS---------------------------------------------------
  public void draw()
  {
    super.draw();
    
    rectMode( CORNERS );
    fill( fillColor );
    stroke( fillColor );
    //roundRect( getX1(), getY1(), getX2(), getY2() );
    
    if( pointsRight )
    {
      drawArrowHeadRight();// getX2(), getY2(), getX1(), getY1());
    }
    else
    {
      drawArrowHeadLeft();// getX1(), getY1(), getX2(), getY2());
    }
    
    if( mouseIsOver() )
    {
      //println( "in gene for loop, noting mouseIsOver " );
      drawHover();
    }
  }
  
  
  public void drawArrowHeadRight()
  {
    if( abs( parentContig.getY1() - parentContig.getY2() ) < 10 )
    {
      strokeWeight( .5f );
      float ymid = (getY2()+getY1())/2;
      line( getX1(), ymid, getX2()-1, ymid );
      return;
    }
    
    strokeWeight( 1 );
    float percOfGeneHeight = 1.0f;
    float h = abs(getY2() - getY1() ) * percOfGeneHeight;
    float ymid = (getY2()+getY1())/2;
    line( getX1(), ymid, getX2()-1, ymid );
    triangle( getX2(), ymid , getX2()-h, ymid-h, getX2()-h, ymid+h);
  }
 
  public void drawArrowHeadLeft()
  {
    if( abs( parentContig.getY1() - parentContig.getY2() ) < 10 )
    {
      strokeWeight( .5f );
      float ymid = (getY2()+getY1())/2;
      line( getX1(), ymid, getX2()-1, ymid );
      return;
    }
    
    strokeWeight( 1 );//(int) abs( getY1() - getY2() ));
    
    float percOfGeneHeight = 1.0f;
    float h = abs(getY2() - getY1() ) * percOfGeneHeight;
    float ymid = (getY2()+getY1())/2;
    line( getX1(), ymid, getX2()-1, ymid );
    triangle( getX1(), ymid , getX1()+h, ymid-h, getX1()+h, ymid+h);
  }
  //------------------------------------------END DRAW METHODS--------------------------------------------------
  
  //------------------------------------------INTERACTIVITY METHODS-----------------------------------------------------
  public boolean mouseIsOver()
  {
    float parentHalfHeight = (parentContig.getY2()-parentContig.getY1())/2;
    return ( mouseX >= (getX1()-1 ) && mouseX <= (getX2()+1 ) && mouseY >= (getY1() - parentHalfHeight) && mouseY <= (getY2() + parentHalfHeight) && parentContig.mouseIsOverPrecise() );
  }
  
  public boolean isOver( float x, float y )
  {
    float parentHalfHeight = (parentContig.getY2()-parentContig.getY1())/2;
    return ( x >= (getX1()-1) && x <= (getX2()+1)  && y >= (getY1() - parentHalfHeight) && y <= (getY2() + parentHalfHeight) );
  }
  
  public void drawHover()
  {
      drawBackgroundHighlight();
      drawInfoBox(); 
      fill( fillColor );
      drawGeneEnlarged();
  }
  
  public void drawHighlightOnly()
  {
    drawBackgroundHighlight();
    drawGeneEnlarged();
  }
  
  public void drawInfoBox()
  {
    int theTextSize = 15;
    textSize( theTextSize );
    String displayText1 = toxinName;
    String displayText2 = start + "-" + stop + " length = " + length + ", " + known;
    
    //draw hover box
    float hoverBoxWidth = textWidth( displayText2 );
    //fill( 100, 100, 100, 75 );
    fill( 10, 10, 10 );//, 0, 0, 200 );
    //fill( highlightColor );
    stroke( highlightColor );
    strokeWeight(2 );
    rectMode( CORNERS );
    //rect( (getX1()+getX2())/2 - hoverBoxWidth/2 , getY1()-3*theTextSize-theTextSize/3, (getX1()+getX2())/2 + hoverBoxWidth/2, getY1()-theTextSize/3 ) ;
    rect( (getX1()+getX2())/2 - hoverBoxWidth/2 , parentContig.getY1()-3*theTextSize-theTextSize/3, (getX1()+getX2())/2 + hoverBoxWidth/2, parentContig.getY1()-theTextSize/3 ) ;

    //draw informational text
    textAlign( CENTER, CENTER );
    fill( 255, 255, 255);
    float x = (getX1()+getX2())/2;
    float y = parentContig.getY1()-2*theTextSize-theTextSize/2;
    if( toxinName.length() > 0 )
      text( displayText1, x, y );
    else
      text( "unknown name", x, y );  
    text( displayText2, x, y + theTextSize + theTextSize/2 );
  }
  
  public void drawBackgroundHighlight()
  {
    fill( highlightColor );//100, 100, 100, 75 );//highlightColor );
    stroke( highlightColor );//100, 100, 100, 75 ); //highlightColor );
    //float percOfGeneHeight = 2.0;
   // float h = abs(getY2() - getY1() ) * percOfGeneHeight;
    rect( getX1()-3, parentContig.getY1(), getX2()+3, parentContig.getY2() );
  }
  
  public void drawGeneEnlarged()
  {
    if( pointsRight )
    {
      drawEnlargedArrowHeadRight();// getX2(), getY2(), getX1(), getY1());
    }
    else
    {
      drawEnlargedArrowHeadLeft();// getX1(), getY1(), getX2(), getY2());
    } 
  }
  
  public void drawEnlargedArrowHeadRight()
  {
    if( abs( getY1() - getY2() ) < 1 )
    {
      //draw back highlight
      //stroke( highlightColor );
      //strokeWeight( 4.0 );
      float ymid = (getY2()+getY1())/2;
      //line( getX1(), ymid, getX2()-tx(1), ymid );
      
      //draw front line
      stroke( fillColor );
      strokeWeight( 4.0f );
      line( getX1(), ymid, getX2()-1, ymid );
      return;
    }
    stroke(fillColor);
    fill( fillColor);
    strokeWeight( 2 );//abs( getY1() - getY2() ) );
    float percOfGeneHeight = 2.0f;
    float h = abs(getY2() - getY1() ) * percOfGeneHeight;
    float ymid = (getY2()+getY1())/2;
    line( getX1(), ymid, getX2()-1, ymid );
    triangle( getX2(), ymid , getX2()-h, ymid-h, getX2()-h, ymid+h);
  }
 
  public void drawEnlargedArrowHeadLeft()
  {
    if( abs( getY1() - getY2() ) < 1 )
    {
      //stroke( highlightColor );
      //strokeWeight( 4.0 );
      float ymid = (getY2()+getY1())/2;
      //line( getX1(), ymid, getX2()-tx(1), ymid );
      
      stroke( fillColor );
      strokeWeight( 4.0f );
      line( getX1(), ymid, getX2()-1, ymid );
      return;
    }
    stroke(fillColor);
    fill( fillColor);
    strokeWeight( 2);//(int) abs( getY1() - getY2() ));
    float percOfGeneHeight = 2.0f;
    float h = abs(getY2() - getY1() ) * percOfGeneHeight;
    float ymid = (getY2()+getY1())/2;
    line( getX1(), ymid, getX2()-1, ymid );
    triangle( getX1(), ymid , getX1()+h, ymid-h, getX1()+h, ymid+h);
  }
  //------------------------------------------END INTERACTIVITY METHODS-------------------------------------------------
  
  //------------------------------------------ANIMATION METHODS--------------------------------------------------

  //---------------------------------------END ANIMATION METHODS-------------------------------------------------
  
  //----------------------------------------------SETTERS-------------------------------------------------------------
  
  public void setColors( int fc, int hc )
  {
    fillColor = fc;
    highlightColor = hc;
  }
  
  public void setColor( int fc )
  {
    println( "set color ");
    fillColor = fc;
  }
  
  public void flip()
  {
    println( "gene flipping" );
    int temp = start;
    start = stop;
    stop = temp;
    pointsRight = !pointsRight;
  }
  
  //---------------------------------------------END SETTERS----------------------------------------------------------
  
  //---------------------------------------------GETTERS---------------------------------------------------------
  public int getStart()
  {
    return start;
  }
  
  public int getStop()
  {
    return stop;
  }
  
  public int getGreatestNT()
  {
    if( stop > start )
      return stop;
    return start;
  }
  
  public String getToxinName()
  {
    return toxinName;
  }
  
  public int getLength()
  {
    return length;
  }
  //--------------------------------------------END GETTERS------------------------------------------------------
  
  //-----------------------------------------------UTILITY METHODS-----------------------------------------------
  public String toString()
  {
    return start + " " + stop + " " + known + " " + toxinName;
  }
  
  public boolean equals(Gene other)
  {
    if( toxinName.equalsIgnoreCase("na") )
     return length == other.getLength();
    return toxinName.equalsIgnoreCase( other.getToxinName() );
  }
}

class Integrator {

  final float DAMPING = .45f;
  final float ATTRACTION = .2f;

  float value;
  float vel;
  float accel;
  float force;
  float mass = 1;

  float damping = DAMPING;
  float attraction = ATTRACTION;
  boolean targeting;
  float target;
  
  float framesPerSec = 60;
  float secPerTransition = 2.0f;
  float rateOfChange;


  Integrator() { }


  Integrator(float value) {
    this.value = value;
    target = value;
  }


  Integrator(float value, float damping, float attraction) {
    this.value = value;
    this.damping = damping;
    this.attraction = attraction;
    target = value;
  }


  public void set(float v) {
    value = v;
  }


  public void update() {
    if(! targeting ){
      return;
    }
    if (targeting) {
      force += attraction * (target - value);      
    }
    if( abs(target - value ) < 1 ){
      value = target;
      targeting = false;
    }
    accel = force / mass;
    vel = (vel + accel) * damping;
    value += vel;
    //if( value < 0 ){
      //value = 0;
    //}

    force = 0;
  }
  
  public void update2(){
    if( !targeting ){
      return;
    }
    if( targeting){
      value += (rateOfChange);
      rateOfChange= rateOfChange*1.05f;
    }
    if( abs( target-value) < 2){
      value = target;
      targeting = false;
    }
  }


  public void target(float t) {
    targeting = true;
    target = t;
  }
  
  
  public void target2(float t){
    //println("target");
    targeting = true;
    target = t;
    rateOfChange = (target - value)/(framesPerSec*secPerTransition);
    //println( "rateOfChange = "+ rateOfChange);
    
  }


  public void noTarget() {
    targeting = false;
  }
  
  public boolean isTargeting(){
    return targeting;
  }
}

/*
public class MouseWheelEventDemo implements MouseWheelListener {
 public MouseWheelEventDemo() {
   addMouseWheelListener(this);
 }
 public void mouseWheelMoved(MouseWheelEvent e) {
   String message;
   int notches = e.getWheelRotation();
   if (notches < 0) {
     message = "Mouse wheel moved UP "
       + -notches + " notch";
   } 
   else {
     message = "Mouse wheel moved DOWN "
       + notches + " notch";
   }
   println(message);
 }
}
*/

float oldMousePosition;
boolean init;

public void initMoveInSpace()
{
  oldMousePosition = 0.0f;
  
  init = false;
}

public void moveInSpace()
{
  if( !init )
  {
    oldMousePosition = mouseY;
    init = true;
    return;
  }
  
  y1Bound.target( y1Bound.value + (oldMousePosition - mouseY)  );
  y2Bound.target( y2Bound.value + (oldMousePosition - mouseY)  );
  oldMousePosition = mouseY;
}

public void stopMoveInSpace()
{
  //y1Bound.target( y1Bound.value + (oldMousePosition - mouseY)  );
  //y2Bound.target( y2Bound.value + (oldMousePosition - mouseY)  );
  
  init = false;
}
class RectObject
{
  Coordinate c1, c2;
  
  RectObject( float x1, float y1, float x2, float y2 )
  {
    c1 = new Coordinate( x1, y1 );
    c2 = new Coordinate( x2, y2 );
  }
  
  RectObject( int x1, int y1, int x2, int y2 )
  {
     this( (float)x1, (float)y1, (float)x2, (float)y2 );
  }
  
//----------------------------------------DRAW METHODS----------------------------------------------
  public void draw()
  {
    if( isTargeting() )
      update(); 
  }
//---------------------------------------END DRAW METHODS------------------------------------------

//------------------------------------------ANIMATION METHODS----------------------------------------
  public boolean isTargeting()
  {
    return c1.isTargeting() || c2.isTargeting() ;
  }
  
  public void target( float x1, float y1, float x2, float y2 )
  {
    c1.target( x1, y1 );
    c2.target( x2, y2 );
  }
  
  public void target( int x1, int y1, int x2, int y2 )
  {
    this.target( (float)x1, (float)y1, (float)x2, (float)y2 );
  }
  
  public void targetX1(float newX1)
  {
    this.target( newX1, c1.y.target, c2.x.target, c2.y.target );
  }
  
  public void targetY1(float newY1)
  {
    this.target( c1.x.target, newY1, c2.x.target, c2.y.target );
  }
  
  public void targetX2(float newX2)
  {
    this.target( c1.x.target, c1.y.target, newX2, c2.y.target );
  }
  
  public void targetY2(float newY2)
  {
    this.target( c1.x.target, c1.y.target, c2.x.target, newY2 );
  }
  
  public void update()
  {
    if( c1.isTargeting() )
      c1.update();
    if( c2.isTargeting() )
      c2.update();
  }

//--------------------------------------END ANIMATION METHODS----------------------------------------

//----------------------------------------MOVEMENT METHODS-------------------------------------------
  public void moveRight( float diff )
  {
    c1.moveRight(diff);
    c2.moveRight(diff);
  }
  
  public void moveRight( int diff )
  {
    this.moveRight( (float)diff );
  }
  
  public void moveLeft( float diff )
  {
    c1.moveLeft(diff);
    c2.moveLeft(diff);
  }
  
  public void moveLeft( int diff )
  {
    this.moveLeft( (float)diff );
  }
  
  public void moveUp( float diff )
  {
    c1.moveUp(diff);
    c2.moveUp(diff);
  }
  
  public void moveUp( int diff )
  {
    this.moveUp( (float)diff );
  }
  
  public void moveDown( float diff )
  {
    c1.moveDown(diff);
    c2.moveDown(diff);
  }
  
  public void moveDown( int diff )
  {
    this.moveDown( (float)diff );
  }
  
  public void moveHorizontally(float diff)
  {
    this.moveRight( diff );
  }
  
  public void moveHorizontally(int diff)
  {
    this.moveRight( (float)diff );
  }
  
  public void moveVertically(float diff)
  {
    this.moveDown( diff );
  }
  
  public void moveVertically( int diff )
  {
    this.moveDown( (float) diff );
  }

  public void moveDiagonally( float diffX, float diffY )
 {
   this.moveRight( diffX );
   this.moveDown( diffY );
 } 
 
 public void moveDiagonally( int diffX, int diffY )
 {
   this.moveDiagonally((float) diffX, (float) diffY );
 }
 
 public void moveCircular( )
 {
   //TO Do
 }
 
 public void moveSquare( )
 {
   //To Do
 }
//----------------------------------------END MOVEMENT METHODS-------------------------------------

//--------------------------------------MODIFY SIZE OF OBJECT---------------------------------------  

//-----------------------INCREASE WIDTH------------------------
  //if modes == 0 , increase from center
  //if mode == 1, increase toward the right
  //if mode == 2 increase toward the left
  public void increaseWidth( float diff, int mode )
  {
    if( mode == 0 )
      increaseWidthCentered( diff );
    else if( mode == 1 )
      increaseWidthToRight( diff );
    else if( mode == 2 )
      increaseWidthToLeft( diff );
  }
  
  public void increaseWidth( int diff, int mode )
  {
    this.increaseWidth( (float) diff, mode );
  }
  
  public void increaseWidthCentered( float diff )
  {
     c1.moveLeft( diff/2 );
     c2.moveRight( diff/2 );
  }
  
  public void increaseWidthCentered( int diff )
  {
     this.increaseWidthCentered( (float)diff );
  }
  
  public void increaseWidthToRight( float diff )
  {
    c2.moveRight( diff );
  }
  
  public void increaseWidthToRight( int diff )
  {
     this.increaseWidthToRight( (float)diff );
  }
  
  public void increaseWidthToLeft( float diff )
  {
    c1.moveLeft( diff );
  }
  
  public void increaseWidthToLeft( int diff )
  {
     this.increaseWidthToLeft( (float)diff );
  }
//-------------------END INCREASE WIDTH------------------------

//--------------------DECREASE WIDTH--------------------------
  //if modes == 0 , increase from center
  //if mode == 1, increase toward the right
  //if mode == 2 increase toward the left
  public void decreaseWidth( float diff, int mode )
  {
    if( mode == 0 )
      this.increaseWidthCentered( -1*diff );
    else if( mode == 1 )
      this.increaseWidthToRight( -1*diff );
    else if( mode == 2 )
      this.increaseWidthToLeft( -1*diff );
  }
  
  public void decreaseWidth( int diff, int mode )
  {
    this.increaseWidth( (float) -1*diff, mode );
  }
  
  public void decreaseWidthCentered( float diff )
  {
    this.increaseWidthCentered( -1*diff );
  }
  
  public void decreaseWidthCentered( int diff )
  {
     this.increaseWidthCentered( (float)-1*diff );
  }
  
  public void decreaseWidthToRight( float diff )
  {
    this.increaseWidthToRight( -1*diff );
  }
  
  public void decreaseWidthToRight( int diff )
  {
     this.increaseWidthToRight( (float)-1*diff );
  }
  
  public void decreaseWidthToLeft( float diff )
  {
    this.increaseWidthToLeft( -1*diff );
  }
  
  public void decreaseWidthToLeft( int diff )
  {
     this.increaseWidthToLeft( (float)-1*diff );
  }
//------------------END DECREASE WIDTH------------------------

//-----------------------INCREASE HEIGHT-----------------------
  public void increaseHeight( float diff )
  {
    this.increaseHeightCentered(diff);
  }
  
  public void increaseHeight( int diff )
  {
    this.increaseHeight( (float) diff );
  }
  
  public void increaseHeight( float diff, int mode )
  {
    if( mode == 0 )
      increaseHeightCentered( diff );
    else if( mode == 1 )
      increaseHeightDown( diff );
    else if( mode == 2 )
      increaseHeightUp( diff );
  }
  
  public void increaseHeight( int diff, int mode )
  {
    this.increaseHeight( (float) diff, mode );
  }
  
  public void increaseHeightCentered( float diff )
  {
     c1.moveUp( diff/2 );
     c2.moveDown( diff/2 );
  }
  
  public void increaseHeightCentered( int diff )
  {
     this.increaseHeightCentered( (float)diff );
  }
  
  public void increaseHeightDown( float diff )
  {
    c2.moveDown( diff );
  }
  
  public void increaseHeightDown( int diff )
  {
     this.increaseHeightDown( (float)diff );
  }
  
  public void increaseHeightUp( float diff )
  {
    c1.moveUp( diff );
  }
  
  public void increaseHeightUp( int diff )
  {
    this.increaseHeightUp( (float)diff );
  }
//--------------------END INCREASE HEIGHT----------------------
 
//-------------------DECREASE HEIGHT---------------------------
 public void decreaseHeight( float diff )
  {
    this.increaseHeightCentered(-1*diff);
  }
  
  public void decreaseHeight( int diff )
  {
    this.increaseHeight( (float) -1*diff );
  }
  
  public void decreaseHeight( float diff, int mode )
  {
    if( mode == 0 )
      increaseHeightCentered( -1*diff );
    else if( mode == 1 )
      increaseHeightDown( -1*diff );
    else if( mode == 2 )
      increaseHeightUp( -1*diff );
  }
  
  public void decreaseHeight( int diff, int mode )
  {
    this.increaseHeight( (float) -1*diff, mode );
  }
  
  public void decreaseHeightCentered( float diff )
  {
     c1.moveUp( -1*diff/2 );
     c2.moveDown( -1*diff/2 );
  }
  
  public void decreaseHeightCentered( int diff )
  {
     this.increaseHeightCentered( (float)-1*diff );
  }
  
  public void decreaseHeightDown( float diff )
  {
    c2.moveDown( -1*diff );
  }
  
  public void decreaseHeightDown( int diff )
  {
     this.increaseHeightDown( (float)-1*diff );
  }
  
  public void decreaseHeightUp( float diff )
  {
    c1.moveUp( -1*diff );
  }
  
  public void decreaseHeightUp( int diff )
  {
     this.increaseHeightUp( (float)-1*diff );
  }

//----------------------END DECREASE HEIGHT-------------------
  public void expandByPixels(float diff)
  {
    this.increaseWidthCentered(diff);
    this.increaseHeightCentered(diff);
  }
  
  public void shrinkByPixels(float diff)
  {
    this.increaseWidthCentered(-1*diff);
    this.increaseHeightCentered(-1*diff);
  }
  
  public void expandByPercentage(float perc)
  {
    float pix = c2.getY()-c1.getX();
    expandByPixels( perc*pix );
  }
  
  public void shrinkByPercentage( float perc )
  {
    float pix = c2.getY()-c1.getX();
    shrinkByPixels( perc*pix );
  }
  
  public void expandByMultiplier( int multiplier )
  {
    //TO DO
  }
 
 //--------------------------------------END MODIFY SIZE OF OBJECT---------------------------------------   

 //-------------------------------------------------GETTERS----------------------------------------------  
  public float getX1()
  {
    return c1.getX();
  }
  
  public float getX2()
  {
    return c2.getX();
  }
  
  public float getY1()
  {
    return c1.getY();
  }
  
  public float getY2()
  {
    return c2.getY();
  }
  
  public float getActualX1()
  {
    return c1.getActualX();
  }
  
  public float getActualX2()
  {
    return c2.getActualX();
  }
  
  public float getActualY1()
  {
    return c1.getActualY();
  }
  
  public float getActualY2()
  {
    return c2.getActualY();
  }
  
  
//----------------------------------------------END GETTERS-------------------------------------------------  
//--------------------------------------------------IS OVER METHODS------------------------------------------
  public boolean mouseIsOver()
  {
    return mouseX >= c1.getX()- 5 &&  mouseX <= c2.getX() + 5 && mouseY >= c1.getY() - 5 && mouseY <= c2.getY() + 5;
  }
  
  public boolean mouseIsOverPrecise()
  {
     return mouseX >= c1.getX() &&  mouseX <= c2.getX() && mouseY >= c1.getY() && mouseY <= c2.getY();
  }
  
  public boolean xyIsOver(float x, float y)
  {
    return x >= c1.getX()- 5 &&  x <= c2.getX() + 5 && y >= c1.getY() - 5 && y <= c2.getY() + 5;
  }
  
  public boolean xyIsOverPrecise(float x, float y)
  {
    return x >= c1.getX() &&  x <= c2.getX() && y >= c1.getY() && y <= c2.getY();
  }
  
//-----------------------------------------------END IS OVER METHODS------------------------------------------  

}//end class
// bounds upon which coordinates are computed
Integrator x1Bound;
Integrator x2Bound;
Integrator y1Bound;
Integrator y2Bound;

float originalX1; 
float originalY1; 
float originalX2; 
float originalY2;

float zoomPerc = .2f;

float xToYRatio;      

boolean zoomInitialized = false;

public void initZoom()
{
  if( zoomInitialized )
    return;
  x1Bound = new Integrator( 0.0f );
  x2Bound = new Integrator( windowWidth );
  y1Bound = new Integrator( 0.0f );
  y2Bound = new Integrator( windowHeight );
  
  originalX1 = 0.0f;
  originalX2 = (float) windowWidth;
  originalY1 = 0.0f;
  originalY2 = (float) windowHeight;
  
  xToYRatio = (float)windowWidth / (float)windowHeight;
  
  zoomInitialized = true;

}

public boolean isZooming()
{
  return x1Bound.isTargeting() && x2Bound.isTargeting() && y1Bound.isTargeting() && y2Bound.isTargeting();
}

public void zoomUpdate()
{
  if( x1Bound.isTargeting() )
    x1Bound.update();
  if( x2Bound.isTargeting() )
    x2Bound.update();
  if( y1Bound.isTargeting() )
    y1Bound.update();
  if( y2Bound.isTargeting() )
    y2Bound.update();

}

public void zoomIn()
{
  println("zoom in");
  float changeInX = zoomPerc * (x2Bound.value - x1Bound.value);
  float changeInY = zoomPerc * (y2Bound.value - y1Bound.value);//changeInX / xToYRatio ;
  
  //println( "x2Bound.value - x1Bound.value = " + (x2Bound.value - x1Bound.value) );  
  //println( "change in y = " + changeInY );
  //x1Bound.target( x1Bound.value + changeInX );
  //x2Bound.target( x2Bound.value - changeInX );
  y1Bound.target( y1Bound.value + changeInY );
  y2Bound.target( y2Bound.value - changeInY );
}

public void zoomOut()
{
  println("zoom out" );
  float changeInX = zoomPerc * (x2Bound.value - x1Bound.value);
  float changeInY = zoomPerc * (y2Bound.value - y1Bound.value);//float changeInY = changeInX / xToYRatio ;

  //x1Bound.target( x1Bound.value - changeInX );
  //x2Bound.target( x2Bound.value + changeInX );
  y1Bound.target( y1Bound.value - changeInY );
  y2Bound.target( y2Bound.value + changeInY );
}

public float tx(float x)
{
  return map( x, x1Bound.value, x2Bound.value, originalX1, originalX2 );//originalX1, originalX2, x1Bound.value, x2Bound.value );
}

public float ty(float y)
{
  return map( y, y1Bound.value, y2Bound.value, originalY1, originalY2 );//originalY1, originalY2, y1Bound.value, y2Bound.value );
}

public float untx(float x)
{
  return map( x, originalX1, originalX2, x1Bound.value, x2Bound.value );//originalX1, originalX2, x1Bound.value, x2Bound.value );
}

public float unty(float y)
{
  return map( y, originalY1, originalY2, y1Bound.value, y2Bound.value );
}
  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#ECE9D8", "cry3_Contig_Viewer_v3" });
  }
}
